﻿// Copyright (c) 2011, Adaptiv Design
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//    * Neither the name of the <ORGANIZATION> nor the names of its contributors may
// be used to endorse or promote products derived from this software without specific
// prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using Alicanto.Api;
using Alicanto.Compilation;
using Alicanto.Compilation.Code;
using Alicanto.Compilation.Code.Blocks;
using Alicanto.Compilation.Code.Blocks.Iteration;
using Alicanto.Compilation.Code.Blocks.Selection;
using Alicanto.Compilation.Code.Operators;
using Alicanto.Compilation.Code.Operators.Assignment;
using Alicanto.Compilation.Code.Operators.Comparison;
using Alicanto.Compilation.Code.Operators.Numeric;
using Alicanto.Compilation.Code.Operators.Primary;
using Alicanto.Compilation.Code.Operators.Sequence;
using Alicanto.Compilation.Code.Operators.String;
using Alicanto.Compilation.Code.SpecialNames;
using Alicanto.Execution;
using Alicanto.Helpers;
using Alicanto.Lexing;

namespace Alicanto
{
    /// <summary>
    /// XPression parser
    /// </summary>
    internal class Parser
    {
        // State
        private Expression _evaluation;
        private Stack<Scope> _scopes;
        private ICodeGenerator _nextGenerator;

        /// <summary>
        /// The domain instance to compile against.
        /// </summary>
        private ExecutionDomain _executionDomain;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="executionDomain">The execution domain wich will be used when running this parsers XPression code.</param>
        internal Parser(ExecutionDomain executionDomain)
        {
            _executionDomain = executionDomain;
        }

        /// <summary>
        /// Parses a sequence of XPression tokens.
        /// </summary>
        /// <returns></returns>
        internal BlockExpression Parse(TokenSequence tokens)
        {
            _scopes = new Stack<Scope>();
            Scope scope = _enterScope();

            // Setup global scope
            scope.AsExecutionContext("", ScopeType.Global);

            // Begin parsing
            List<Expression> expressions = new List<Expression>();
            foreach (Expression statement in _parse(tokens))
            {
                expressions.Add(statement);
            }

            expressions.Insert(0, _leaveScope());

            // Global expression block
            BlockExpression result = Expression.Block(
                scope.Declarations,
                expressions
            );

            return result;
        }

        #region Core Logic

        /// <summary>
        /// Enters a new compilation scope.
        /// </summary>
        /// <returns></returns>
        private Scope _enterScope()
        {
            Scope scope = new Scope(_executionDomain, _scopes.Count > 0 ? _scopes.Peek() : null);
            _scopes.Push(scope);

            return scope;
        }

        /// <summary>
        /// Leaves the current compilations scope.
        /// </summary>
        private Expression _leaveScope()
        {
            Scope scope = _scopes.Peek();
            foreach (DelayedCode delayed in scope.Delays)
            {
                _generate(delayed.Code, delayed.Tokens, true);
            }

            _scopes.Pop();
            return Expression.Assign(
                Expression.PropertyOrField(
                    Expression.Constant(scope.ExecutionContext),
                    "RuntimeVariables"
                ),
                Expression.RuntimeVariables(scope.Declarations)
            );
        }

        /// <summary>
        /// Keeps parsing the suplied tokens into one or multiple expressions, until end of block has been reached.
        /// </summary>
        /// <param name="tokens"></param>
        /// <returns></returns>
        private IEnumerable<Expression> _parse(TokenSequence tokens)
        {
            while (tokens.Available > 0)
            {
                switch (tokens.Peek().Type)
                {
                    case TokenType.Structural:
                        Token current = tokens.Pop();
                        break;

                    // Block begin
                    case TokenType.Block:
                        yield return _generate(_block(tokens.Pop()), tokens);
                        break;

                    // Block end
                    case TokenType.End:
                        tokens.Pop();
                        yield break;

                    // Block end and Block begin
                    case TokenType.EndBlock:
                        yield break;

                    // Command
                    case TokenType.Command:
                        yield return _generate(_command(tokens.Pop()), tokens);
                        break;

                    // Template
                    case TokenType.Template:
                        yield return _template(tokens.Pop());
                        break;

                    // Comment
                    case TokenType.Comment:
                        tokens.Pop();
                        break;

                    // Expression statement
                    default:
                        // Ensure correct operator presedence first
                        Precedence.ApplyOperatorPrecedence(tokens);
                        yield return _expression(tokens);
                        break;
                }
            }
        }

        /// <summary>
        /// Keeps parsing the supplied tokens into one ore multiple expressions, until the close token has been encountered.
        /// </summary>
        /// <param name="tokens"></param>
        /// <param name="tokenOpen"></param>
        /// <param name="tokenClose"></param>
        /// <returns></returns>
        private IEnumerable<Expression> _sequence(TokenSequence tokens, Token tokenOpen, Token tokenClose)
        {
            if (tokens.Peek() != tokenOpen)
                yield break; // No sequence

            // Enter sequence
            tokens.Pop();
            int open = 1;

            TokenSequence element = new TokenSequence();
            while (tokens.Available > 0 && open > 0)
            {
                Token current = tokens.Pop();
                if (current == tokenOpen)
                    open++;
                else if (current == tokenClose)
                    open--;

                if (open == 0)
                {
                    if (element.Available > 0)
                        element.Push(Token.Comma);
                }
                else
                    element.Push(current);

                if ((open == 0 || (current == Token.Comma && open == 1)) && element.Available > 0)
                {
                    // Parse element
                    Expression expression = _expression(element.Skip(Token.Semicolon));
                    if (expression.Type != typeof(void))
                        yield return expression;
                }
            }

            if (open > 0)
                // Sequence not closed
                throw new Exception("Unexpected end of sequence");

            // Finished
            yield break;
        }

        /// <summary>
        /// Parses one expression.
        /// </summary>
        /// <param name="tokens"></param>
        /// <returns></returns>
        private Expression _expression(TokenSequence tokens)
        {
            _evaluation = Expression.Empty();
            while (tokens.Available > 0)
            {
                Token current = tokens.Pop();
                switch (current.Type)
                {
                    // Literal
                    case TokenType.Literal:
                        _evaluation = _literal(current);
                        break;

                    // Simple name
                    case TokenType.SimpleName:
                        _evaluation = _simpleName(current, tokens);
                        break;

                    // Special name
                    case TokenType.SpecialName:
                        _evaluation = _generate(_specialName(current), tokens);
                        break;

                    // Operator
                    case TokenType.Operator:
                        _evaluation = _generate(_operator(current), tokens);

                        if (!Token.IsUnaryOperator(current))
                            return _evaluation;

                        break;

                    // Structural
                    case TokenType.Structural:
                        if (current == Token.Semicolon
                            || current == Token.Comma
                            || current == Token.BracketClose)
                        {
                            return _evaluation;
                        }
                        else if (current == Token.BracketOpen)
                        {
                            _evaluation = _expression(tokens);
                            break;
                        }
                        else if (current == Token.SquareBracketOpen)
                        {
                            // Array initializer
                            if (_evaluation.Type == typeof(void))
                            {
                                _evaluation = _array(tokens);
                            }
                            // Array access
                            else
                            {
                                _evaluation = _arrayAccess(tokens);
                            }

                            break;
                        }
                        else if (current == Token.Colon)
                        {
                            return Expression.Convert(
                                _evaluation,
                                Typing.FromString(tokens.Pop(TokenType.SimpleName).Str)
                            );
                        }

                        throw new Exception("Unknown structural token.");

                    default:
                        throw new Exception();
                }
            }

            // Unexpected end of tokens
            throw new Exception("Unexpected end of tokens.");
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="generator"></param>
        /// <param name="tokens"></param>
        /// <returns></returns>
        private Expression _generate(ICodeGenerator generator, TokenSequence tokens) { return _generate(generator, tokens, false); }
        private Expression _generate(ICodeGenerator generator, TokenSequence tokens, bool isDelayed)
        {
            if (_nextGenerator != null)
            {
                generator = _nextGenerator;
                _nextGenerator = null;
            }

            // Handle delayed parsing
            if (generator is IRequiresDelayedParsing && !isDelayed)
            {
                _scopes.Peek().Delays.Add(
                    new DelayedCode(
                        generator,
                        (generator as IRequiresDelayedParsing).SkipTokens(tokens)
                    )
                );

                return generator.Code;
            }

            // State
            Scope scope = null;
            bool omit = false;

            foreach (ParseInstruction instruction in generator.Generate(tokens, _scopes.Peek()))
            {
                Type instructionType = instruction.GetType();

                // Parse until the end of a statement block
                if (instructionType == typeof(Parse))
                {
                    (instruction as Parse).Expressions = _parse(tokens).ToList<Expression>();
                }
                // Parse a full expression (including operator presedence parsing)
                else  if (instructionType == typeof(ParseExpression))
                {
                    Precedence.ApplyOperatorPrecedence(tokens);
                    (instruction as ParseExpression).Expression = _expression(tokens);
                }
                // Parse the remainder of the current expression
                else if (instructionType == typeof(ParseExpressionRemainder))
                {
                    (instruction as ParseExpressionRemainder).Remainder = _expression(tokens);
                }
                else if (instructionType == typeof(ParseBlock))
                {
                    (instruction as ParseBlock).Block = _generate(
                        _block(
                            tokens.Skip(Token.Semicolon).Pop(TokenType.Block)
                        ),
                        tokens
                    );
                }
                // Parse a sequence of expressions
                else if (instruction.GetType() == typeof(ParseSequence))
                {
                    (instruction as ParseSequence).Expressions = _sequence(
                            tokens,
                            (instruction as ParseSequence).TokenOpen,
                            (instruction as ParseSequence).TokenClose
                        ).ToList<Expression>();
                }
                else if (instruction.GetType() == typeof(GetEvaluation))
                {
                    (instruction as GetEvaluation).Expression = _evaluation;
                }
                else if (instruction.GetType() == typeof(EnterScope))
                {
                    scope = _enterScope();
                    (instruction as EnterScope).Scope = scope;
                }
                else if (instruction.GetType() == typeof(LeaveScope))
                {
                    (instruction as LeaveScope).Finalizer = _leaveScope();
                }
                else if (instruction.GetType() == typeof(OmitCode))
                {
                    omit = true;
                }
                else if (instruction.GetType() == typeof(GenerateNextTokenWith))
                {
                    _nextGenerator = (instruction as GenerateNextTokenWith).Generator;
                }
            }

            Expression result = generator.Code;
            if (omit)
                result = _evaluation;

            if (result == null)
            {
                throw new Exception();
            }

            return result;
        }

        #endregion

        #region Variable handling

        /// <summary>
        /// Creates a new variable.
        /// </summary>
        /// <param name="scope"></param>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private ParameterExpression _declare(Scope scope, Type type, string name)
        {
            if (name == null)
                throw new Exception("Variable name required.");

            if (scope.Type == ScopeType.Class)
                throw new Exception();

            ParameterExpression var = Expression.Parameter(type, name);
            _scopes.Peek().Declare(name, var);

            return var;
        }

        /// <summary>
        /// Resolves the given token to a variable and updates the scope(s) involved.
        /// </summary>
        /// <param name="token"></param>
        /// <param name="tokens"></param>
        /// <returns></returns>
        private Expression _simpleName(Token token, TokenSequence tokens)
        {
            Expression variable = null;
            Scope scope = _scopes.Peek();

            if(!scope.IsCapturingArguments)
                variable = scope[token.Str];

            if (variable == null)
            {
                // Look in execution context
                if (!scope.IsCapturingArguments && scope.ExecutionContext.HasMember(token.Str))
                {
                    variable = Expression.Constant(scope.ExecutionContext);
                    tokens.Insert(token);
                    tokens.Insert(Token.OpMemberAccess);
                }
                else
                {
                    // Define
                    Type type = typeof(object);
                    if (tokens.Peek() == Token.Colon)
                    {
                        tokens.Pop();
                        type = Typing.FromString(tokens.Pop(TokenType.SimpleName).Str);
                    }

                    variable = _declare(scope, type, token.Str);
                    scope[token.Str] = (ParameterExpression)variable;
                }
            }

            return variable;
        }

        private ICodeGenerator _specialName(Token token)
        {
            // @
            if (token == Token.ExecutionContext)
                return new ResolveContext();
            // <=
            else if (token == Token.ReturnHere)
                return new ReturnToMe();

            throw new Exception();
        }

        #endregion

        #region Array handling

        private Expression _array(TokenSequence tokens)
        {
            tokens.Insert(Token.SquareBracketOpen);
            List<Expression> elements = _sequence(tokens, Token.SquareBracketOpen, Token.SquareBracketClose).ToList<Expression>();

            List<Expression> safe = new List<Expression>();

            foreach (Expression element in elements)
            {
                safe.Add(Expression.Convert(element, typeof(object)));
            }

            return Expression.ListInit(
                Expression.New(typeof(List<object>)),
                safe
            );
        }

        private Expression _arrayAccess(TokenSequence tokens)
        {
            Expression array = _evaluation;

            tokens.Insert(Token.SquareBracketOpen);
            List<Expression> arguments = _sequence(tokens, Token.SquareBracketOpen, Token.SquareBracketClose).ToList<Expression>();

            if (arguments.Count == 1)
            {
                return Expression.Property(
                    Conversion.Ensure(array, typeof(IList)),
                    typeof(IList).GetProperty("Item"),
                    arguments
                );
            }
            else if (arguments.Count == 2)
            {

            }

            throw new Exception();
        }

        #endregion

        private Expression _literal(Token token)
        {
            return Expression.Constant(token.Constant());
        }

        private Expression _template(Token token)
        {
            return Expression.Call(
               null,
               typeof(Globals).GetMethod("print", new Type[] { typeof(string) }),
               Expression.Constant(token.Str)
           );
        }

        private ICodeGenerator _block(Token begin)
        {
            // if
            if (begin == Token.If)
                return new If();
            // foreach
            else if (begin == Token.Foreach)
                return new Foreach();
            // Function
            else if (begin == Token.Function)
                return new Function();
            // Class
            else if (begin == Token.Clazz)
                return new Clazz();

            throw new Exception("Unrecognized block: " + begin + ".");
        }

        private ICodeGenerator _command(Token begin)
        {
            if (begin == Token.Return)
                return new Return();
            else if (begin == Token.Define)
                return new Def();

            throw new Exception("Unrecognized command: " + begin + ".");
        }

        private ICodeGenerator _operator(Token op)
        {
            // .
            if (op == Token.OpMemberAccess)
                return new OpMemberAccess();
            // =
            else if (op == Token.OpAssign)
                return new OpAssign();
            // +
            else if (op == Token.OpAdd)
                return new OpAdd();
            // -
            else if (op == Token.OpSubtract)
                return new OpSubtract();
            // *
            else if (op == Token.OpMultiply)
                return new OpMultiply();
            // &
            else if (op == Token.OpConcat)
                return new OpConcat();
            // ...
            else if (op == Token.OpEnumerable)
                return new OpEnumerable();
            // ==
            else if (op == Token.OpEqual)
                return new OpEqual();
            // >
            else if (op == Token.OpCall)
                return new OpCall();

            throw new Exception("Unrecognized operator: " + op + ".");
        }
    }
}
